package com.leyou.order.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.wxpay.sdk.WXPayUtil;
import com.leyou.common.auth.entity.UserHolder;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exceptions.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.IdWorker;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.SkuDTO;
import com.leyou.order.config.PayProperties;
import com.leyou.order.dto.CartDTO;
import com.leyou.order.dto.OrderDTO;
import com.leyou.order.entity.TbOrder;
import com.leyou.order.entity.TbOrderDetail;
import com.leyou.order.entity.TbOrderLogistics;
import com.leyou.order.entity.TbOrderSeckillDetail;
import com.leyou.order.enums.BusinessTypeEnum;
import com.leyou.order.enums.OrderStatusEnum;
import com.leyou.order.vo.OrderDetailVO;
import com.leyou.order.vo.OrderLogisticsVO;
import com.leyou.order.vo.OrderVO;
import com.leyou.seckill.client.SeckillClient;
import com.leyou.seckill.dto.OrderSecKillDTO;
import com.leyou.seckill.dto.SeckillPolicyDTO;
import com.leyou.user.client.UserClient;
import com.leyou.user.dto.UserAddressDTO;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
import sun.net.www.http.HttpClient;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class OrderService {

    @Autowired
    private TbOrderService orderService;
    @Autowired
    private TbOrderDetailService orderDetailService;
    @Autowired
    private TbOrderSeckillDetailService orderSeckillDetailService;
    @Autowired
    private TbOrderLogisticsService orderLogisticsService;

    @Autowired
    private ItemClient itemClient;

    @Autowired
    private UserClient userClient;
    
    @Autowired
    private IdWorker idWorker;

    @Transactional
    public Long addOrder(OrderDTO orderDTO, String userId) {
//        private Long addressId;
//        private List<CartDTO> carts;
//        private Integer paymentType;

        long orderId = idWorker.nextId(); //分布式id生成算法  推特 雪花算法

        TbOrder tbOrder = new TbOrder();


//        保存订单表
//页面传递的有：
    //`payment_type` tinyint(1) unsigned zerofill NOT NULL COMMENT '支付类型，1、在线支付，2、货到付款',
        tbOrder.setPaymentType(orderDTO.getPaymentType()); //   需要在实体类中把boolean修改成Integer 0  1
//后台需要赋值：
    //`order_id` bigint(20) NOT NULL COMMENT '订单id',
        tbOrder.setOrderId(orderId);
    //`user_id` bigint(20) NOT NULL COMMENT '用户id',
        tbOrder.setUserId(Long.parseLong(userId));
    //`status` tinyint(1) DEFAULT NULL COMMENT '订单的状态，1、未付款 2、已付款,未发货 3、已发货,未确认 4、确认收货，交易成功 5、交易取消，订单关闭 6、交易结束，已评价',
        tbOrder.setStatus(OrderStatusEnum.INIT.value());   //  需要在实体类中把boolean修改成Integer 0  1
        //`b_type` tinyint(1) unsigned zerofill NOT NULL COMMENT '订单业务类型1- 商城订单 2、秒杀订单',
        tbOrder.setBType(BusinessTypeEnum.MALL.value());
        //` total_fee`` bigint(20) NOT NULL COMMENT '总金额，单位为分',
//        在后台需要重新计算
        List<CartDTO> carts = orderDTO.getCarts();
//        private Long skuId; private Integer num;
//        private Long skuId; private Integer num;

//         把 List<CartDTO> carts转成一个map 目的就是为了更方便的根据skuId获取num
        Map<Long, Integer> skuIdAndNumMap = carts.stream().collect(Collectors.toMap(CartDTO::getSkuId, CartDTO::getNum));
        List<Long> skuIds = carts.stream().map(CartDTO::getSkuId).collect(Collectors.toList());
        List<SkuDTO> skuList = itemClient.findSkuListBySkuIds(skuIds);
        Long totalFee = 0L;

        List<TbOrderDetail> orderDetailList = new ArrayList<>();//存储的就是需要保存的TbOderDetail
        for (SkuDTO sku : skuList) {    //总金额 =  所有sku价格*数量
            Integer num = 0;
            num = skuIdAndNumMap.get(sku.getId());
            totalFee+= sku.getPrice() * num;

//       保存订单详情表
            TbOrderDetail tbOrderDetail = BeanHelper.copyProperties(sku,TbOrderDetail.class);
            tbOrderDetail.setSkuId(sku.getId());
            tbOrderDetail.setNum(num);
            tbOrderDetail.setOrderId(orderId);
            tbOrderDetail.setImage(StringUtils.substringBefore(sku.getImages(),","));

            orderDetailList.add(tbOrderDetail);
        }
        tbOrder.setTotalFee(totalFee);
        //` actual_fee` bigint(20) NOT NULL COMMENT '实付金额。单位:分。如:20007，表示:200元7分'
        tbOrder.setPostFee(0L);
        tbOrder.setActualFee(tbOrder.getTotalFee());


//        先根据页面上传过来的addressId 查询 UserAddress对象
        UserAddressDTO userAddressDTO = userClient.findUserAddressById(orderDTO.getAddressId());
//       保存物流信息表
        TbOrderLogistics tbOrderLogistics = BeanHelper.copyProperties(userAddressDTO,TbOrderLogistics.class);
        tbOrderLogistics.setOrderId(orderId);

//        在这里统一使用相关的service保存三张表的数据
        boolean save = orderService.save(tbOrder);
        if(!save){
            throw new LyException(ExceptionEnum.INSERT_OPERATION_FAIL);
        }
        save = orderDetailService.saveBatch(orderDetailList);
        if(!save){
            throw new LyException(ExceptionEnum.INSERT_OPERATION_FAIL);
        }
        save = orderLogisticsService.save(tbOrderLogistics);
        if(!save){
            throw new LyException(ExceptionEnum.INSERT_OPERATION_FAIL);
        }
//        减库存  修改sku的库存数 通过feign的方式调用一个减库存的方法

//        update  tb_sku set stock = stock-#{num} where sku_id=#{skuId}
//        把多个sku和sku对应的num传递到 item微服务中
//        skuIdAndNumMap
        itemClient.stockMinus(skuIdAndNumMap);

        return orderId;
    }

    public OrderVO findOrderById(Long orderId) {

        String userId = UserHolder.getUserId();
//        查询三个表的数据
//        TbOrder tbOrder = orderService.getById(orderId); //getById==getByPrimaryKey  不要忘了在实体类的orderId属性上添加@TableId
//   有个弊端，只要一个OrderId就能把订单查询出来，不分是谁
//        加限制：  orderId  userId  未支付的status

        QueryWrapper<TbOrder> qw = new QueryWrapper<>();
        qw.lambda().eq(TbOrder::getOrderId,orderId);
        qw.lambda().eq(TbOrder::getUserId,Long.parseLong(userId));
        qw.lambda().eq(TbOrder::getStatus,OrderStatusEnum.INIT.value());
        TbOrder tbOrder  = orderService.getOne(qw);
        if(tbOrder==null){
            throw new LyException(ExceptionEnum.ORDER_NOT_FOUND);
        }

        OrderVO orderVO = BeanHelper.copyProperties(tbOrder,OrderVO.class);
//        订单和物流是一对一的关系

        TbOrderLogistics tbOrderLogistics = orderLogisticsService.getById(orderId); //getById==getByPrimaryKey   不要忘了在实体类的orderId属性上添加@TableId
        OrderLogisticsVO orderLogisticsVO = BeanHelper.copyProperties(tbOrderLogistics, OrderLogisticsVO.class);
        orderVO.setLogistics(orderLogisticsVO);

        QueryWrapper<TbOrderDetail> queryWrapper = new QueryWrapper();
        queryWrapper.lambda().eq(TbOrderDetail::getOrderId,orderId);
        List<TbOrderDetail> tbOrderDetailList = orderDetailService.list(queryWrapper);

        List<OrderDetailVO> orderDetailVOS = BeanHelper.copyWithCollection(tbOrderDetailList, OrderDetailVO.class);
        orderVO.setDetailList(orderDetailVOS);

        return orderVO;
    }

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private PayProperties payProperties;

    @Autowired
    private StringRedisTemplate redisTemplate;

    private String prefix = "ly:order:pay:";
    public String getCodeUrl(Long orderId) {
//        Spring封装HttpClient 提供一个工具RestTemplate
        try {
//            先从redis中获取，获取不到就应该调用微信的api
            String code_url = redisTemplate.boundValueOps(prefix + orderId).get();
            if(StringUtils.isNotBlank(code_url)){
                return code_url;
            }

            String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";

            Map<String,String> paramMap = new HashMap();
            paramMap.put("appid",payProperties.getAppID());
            paramMap.put("mch_id",payProperties.getMchID());
            paramMap.put("nonce_str",WXPayUtil.generateNonceStr());
            paramMap.put("body","乐优商城支付");
            paramMap.put("out_trade_no",orderId.toString());
            paramMap.put("total_fee","1"); // TODO 实际中这里应该是当前订单真是的金额
            paramMap.put("spbill_create_ip","127.0.0.1");
            paramMap.put("notify_url",payProperties.getNotifyurl()); // 支付成后 微信会调用一个地址
            paramMap.put("trade_type","NATIVE"); // 支付成后 微信会调用一个地址

            String paramXml = WXPayUtil.generateSignedXml(paramMap, payProperties.getKey());//把map转成带有签名xml字符串
            String resultXml = restTemplate.postForObject(url, paramXml, String.class); //调用统一下单方法
            Map<String, String> resultMap = WXPayUtil.xmlToMap(resultXml);
            code_url = resultMap.get("code_url");
//            获取到code_url后缓存到redis中
            redisTemplate.boundValueOps(prefix+orderId).set(code_url,2, TimeUnit.HOURS);
            return code_url;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }

    public Integer queryOrderPayState(Long orderId) {
        //        Spring封装HttpClient 提供一个工具RestTemplate
        try {
            String url = "https://api.mch.weixin.qq.com/pay/orderquery";
            Map<String,String> paramMap = new HashMap();
            paramMap.put("appid",payProperties.getAppID());
            paramMap.put("mch_id",payProperties.getMchID());
            paramMap.put("nonce_str",WXPayUtil.generateNonceStr());
            paramMap.put("out_trade_no",orderId.toString());
            String paramXml = WXPayUtil.generateSignedXml(paramMap, payProperties.getKey());//把map转成带有签名xml字符串
            String resultXml = restTemplate.postForObject(url, paramXml, String.class); //调用统一下单方法
            Map<String, String> resultMap = WXPayUtil.xmlToMap(resultXml);
            String trade_state = resultMap.get("trade_state");
            if("SUCCESS".equals(trade_state)){
//                修改订单的状态和支付时间
//                update tb_order set status=2 , pay_time= 当前时间  where order_id=？
                TbOrder tbOrder = new TbOrder();
                tbOrder.setOrderId(orderId);
                tbOrder.setStatus(OrderStatusEnum.PAY_UP.value());
                tbOrder.setPayTime(new Date());
                orderService.updateById(tbOrder); //更新支付状态和时间

                return 1;
            }else{
                return 0;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    @Transactional
    public void cleanOvertimeOrder() {
//        1、把超时订单的商品和数量查询
        List<TbOrderDetail> orderDetailList = orderDetailService.findOvertimeOrderDetail();
//        2、修改超时订单的状态
        orderService.updateOvertimeStatus();
//        3、恢复库存  通过feign远程调用item的微服务
//        update tb_sku set stock= stock + num  where sku_id=?
        Map<Long, Integer> skuIdAndNumMap = orderDetailList.stream().collect(Collectors.toMap(TbOrderDetail::getSkuId, TbOrderDetail::getNum));

        itemClient.stockPlus(skuIdAndNumMap);
//          update  tb_sku set stock = stock-#{num} where sku_id=#{skuId}
    }

    @Autowired
    private SeckillClient seckillClient;

    public void saveSeckillOrder(OrderSecKillDTO orderSecKillDTO) {
//                1、保存订单表
//                2、保存秒杀商品订单详情表
//                3、物流表
//                4、减库存
//        private Long addressId;
//        private List<CartDTO> carts;
//        private Integer paymentType;
        Long seckillId = orderSecKillDTO.getSeckillId();

        long orderId = orderSecKillDTO.getOrderId(); //分布式id生成算法  推特 雪花算法
        TbOrder tbOrder = new TbOrder();
        tbOrder.setPaymentType(orderSecKillDTO.getPaymentType()); //   需要在实体类中把boolean修改成Integer 0  1
        tbOrder.setOrderId(orderId);
        tbOrder.setUserId(orderSecKillDTO.getUserId());
        tbOrder.setStatus(OrderStatusEnum.INIT.value());   //  需要在实体类中把boolean修改成Integer 0  1
        tbOrder.setBType(BusinessTypeEnum.SEC_KILL.value());
        //` total_fee`` bigint(20) NOT NULL COMMENT '总金额，单位为分',
//        调用seckill-service的 feign的接口
        SeckillPolicyDTO seckillPolicy = seckillClient.findSecKillPolicyById(seckillId);
        tbOrder.setTotalFee(seckillPolicy.getCostPrice());  //就是秒杀商品的秒杀价钱
        //` actual_fee` bigint(20) NOT NULL COMMENT '实付金额。单位:分。如:20007，表示:200元7分'
        tbOrder.setPostFee(0L);
        tbOrder.setActualFee(tbOrder.getTotalFee());



//       保存订单详情表
       TbOrderSeckillDetail orderDetail = new TbOrderSeckillDetail();
        orderDetail.setOrderId(orderId);
        orderDetail.setNum(1);
        orderDetail.setSeckillId(seckillId);

//        先根据页面上传过来的addressId 查询 UserAddress对象
        UserAddressDTO userAddressDTO = userClient.findUserAddressById(orderSecKillDTO.getAddressId());
//       保存物流信息表
        TbOrderLogistics tbOrderLogistics = BeanHelper.copyProperties(userAddressDTO,TbOrderLogistics.class);
        tbOrderLogistics.setOrderId(orderId);

//        在这里统一使用相关的service保存三张表的数据
        boolean save = orderService.save(tbOrder);
        if(!save){
            throw new LyException(ExceptionEnum.INSERT_OPERATION_FAIL);
        }
        save = orderSeckillDetailService.save(orderDetail);
        if(!save){
            throw new LyException(ExceptionEnum.INSERT_OPERATION_FAIL);
        }
        save = orderLogisticsService.save(tbOrderLogistics);
        if(!save){
            throw new LyException(ExceptionEnum.INSERT_OPERATION_FAIL);
        }
//        减库存  修改sku的库存数 通过feign的方式调用一个减库存的方法
//        update  tb_sku set stock = stock-#{num} where sku_id=#{skuId}
//        把多个sku和sku对应的num传递到 item微服务中
//        skuIdAndNumMap
        seckillClient.stockMinus(seckillId);
    }

    public OrderVO findSeckillOrder(Long orderId) {
        String userId = UserHolder.getUserId();

        QueryWrapper<TbOrder> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(TbOrder::getOrderId,orderId).eq(TbOrder::getUserId,Long.parseLong(userId)).eq(TbOrder::getStatus,OrderStatusEnum.INIT.value());
        TbOrder tbOrder = orderService.getOne(queryWrapper);

        if(tbOrder==null){
            throw new LyException(ExceptionEnum.ORDER_NOT_FOUND);
        }
        return BeanHelper.copyProperties(tbOrder,OrderVO.class);
    }
}